package com.gitee.mazhenggg.demo.rpc.handler;

import com.gitee.mazhenggg.demo.rpc.beans.MethodMetadata;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author mazhenggg
 */
@Slf4j
public class ReFeignClientHandler implements InvocationHandler {
    private String url;
    private BeanFactory beanFactory;

    public ReFeignClientHandler(String url, BeanFactory beanFactory){
        this.url = url;
        this.beanFactory = beanFactory;
    }

    @Override
    public Object invoke(Object proxy, Method method,
                         Object[] args) {
        MethodMetadata methodMetadata = new MethodMetadata();
        extractUrlAndMethod(method, methodMetadata);
        extractRequestParamAndBody(method, args, methodMetadata);
        extractReturnInfo(method, methodMetadata);
        log.info("methodInfo:" + methodMetadata);
        return invokeRest(methodMetadata);
    }

    private void extractReturnInfo(Method method,
                                   MethodMetadata methodMetadata) {
        // 返回flux还是mono
        // isAssignableFrom 判断类型是否某个的子类
        // instanceof 判断实例是否某个的子类
        boolean isFlux = method.getReturnType()
                .isAssignableFrom(Flux.class);
        methodMetadata.setReturnFlux(isFlux);
        // 得到返回对象的实际类型
        Class<?> elementType = extractElementType(
                method.getGenericReturnType());
        methodMetadata.setReturnElementType(elementType);
    }

    /**
     * 得到泛型类型的实际类型
     *
     * @param genericReturnType
     * @return
     */
    private Class<?> extractElementType(Type genericReturnType) {
        Type[] actualTypeArguments = ((ParameterizedType) genericReturnType)
                .getActualTypeArguments();

        return (Class<?>) actualTypeArguments[0];
    }

    /**
     * 得到请求的param和body
     *
     * @param method
     * @param args
     * @param methodInfo
     */
    private void extractRequestParamAndBody(Method method, Object[] args, MethodMetadata methodInfo) {
        Parameter[] parameters = method.getParameters();
        Map<String, Object> params = new LinkedHashMap<>();
        methodInfo.setParams(params);
        for (int i = 0; i < parameters.length; i++) {
            PathVariable annoPath = parameters[i]
                    .getAnnotation(PathVariable.class);
            if (annoPath != null) {
                params.put(annoPath.value(), args[i]);
            }
            RequestBody annoBody = parameters[i]
                    .getAnnotation(RequestBody.class);
            if (annoBody != null) {
                methodInfo.setBody((Mono<?>) args[i]);
                // 请求对象的实际类型
                methodInfo.setBodyElementType(
                        extractElementType(parameters[i]
                                .getParameterizedType()));
            }
        }
    }


    private void extractUrlAndMethod(Method method,
                                     MethodMetadata methodMetadata) {
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof GetMapping) {
                GetMapping a = (GetMapping) annotation;
                methodMetadata.setUrl(a.value()[0]);
                methodMetadata.setMethod(HttpMethod.GET);
            }
            else if (annotation instanceof PostMapping) {
                PostMapping a = (PostMapping) annotation;
                methodMetadata.setUrl(a.value()[0]);
                methodMetadata.setMethod(HttpMethod.POST);
            }
            else if (annotation instanceof PutMapping) {
                PutMapping a = (PutMapping) annotation;
                methodMetadata.setUrl(a.value()[0]);
                methodMetadata.setMethod(HttpMethod.PUT);
            }
            else if (annotation instanceof DeleteMapping) {
                DeleteMapping a = (DeleteMapping) annotation;
                methodMetadata.setUrl(a.value()[0]);
                methodMetadata.setMethod(HttpMethod.DELETE);
            }
            else if (annotation instanceof RequestMapping) {
                RequestMapping methodMapping = (RequestMapping)annotation;
                RequestMethod[] methods = methodMapping.method();
                if (methods.length == 0) {
                    methods = new RequestMethod[]{RequestMethod.GET};
                }
                methodMetadata.setMethod(HttpMethod.valueOf(methods[0].name()));
                if (methodMapping.value().length > 0) {
                    String pathValue = methodMapping.value()[0];
                    if (pathValue != null && !pathValue.isEmpty()) {
                        methodMetadata.setUrl(pathValue);
                    }
                }
            }

        }
    }



    public Object invokeRest(MethodMetadata methodMetadata) {
        // 返回结果
        Object result = null;
        //获取负载均衡的 webClient
        WebClient.Builder bean = beanFactory.getBean(WebClient.Builder.class);
        WebClient client=bean.build();
        String path = getPath(methodMetadata.getUrl());
        WebClient.RequestBodySpec request = client
                .method(methodMetadata.getMethod())
                .uri(url+path, methodMetadata.getParams())
                .accept(MediaType.APPLICATION_JSON);
        WebClient.ResponseSpec retrieve = null;
        // 判断是否带了body
        if (methodMetadata.getBody() != null) {
            // 发出请求
            retrieve = request
                    .body(methodMetadata.getBody(), methodMetadata.getBodyElementType())
                    .retrieve();
        } else {
            retrieve = request.retrieve();
        }

        // 处理异常
        retrieve.onStatus(status -> status.value() == 404,
                response -> Mono.just(new RuntimeException("404 Not Found")));

        // 处理body
        if (methodMetadata.isReturnFlux()) {
            result = retrieve.bodyToFlux(methodMetadata.getReturnElementType());
        } else {
            result = retrieve.bodyToMono(methodMetadata.getReturnElementType());
        }

        return result;
    }

    String getPath(String path) {
        if (StringUtils.hasText(path)) {
            path = path.trim();
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (path.endsWith("/")) {
                path = path.substring(0, path.length() - 1);
            }
        }
        return path;
    }
}
